home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Software Vault: The Diamond Collection
/
The Diamond Collection (Software Vault)(Digital Impact).ISO
/
cdr42
/
vat041.zip
/
SOUND.C
< prev
next >
Wrap
C/C++ Source or Header
|
1995-03-19
|
68KB
|
2,144 lines
/**************************************************************************
VARMINT'S AUDIO TOOLS
Source code: SOUND.C
Turbo C++ V 3.1
Be careful about the memory model! The definition for
SAMPLE will define what model you should use.
Written by: Peter Sprenger and
Eric Jorgensen (Feb, 1995)
smeagol@rt66.com
sound.c is an adaptation (by Eric Jorgensen) from Peter Sprenger's
SoundX library. Eric's modifications (In part) are as follows:
- Removal of all VOC, PLAY, and MIXER functions
- reprogramming of DMA functions
- Addition of MIDI functions
- Addition of an interrupt driven sound handler
- Addition of numerous comments (about 98% of all comments are Eric's)
- Consolidation of all functions into a single file
- Rewriting of some of Peter's original functions to improve
readability and performance.
----------------------------------------------------------------------
Peter Sprenger's Original Copywrite is as follows:
* Copyright 1993 by Peter Sprenger Pete@amber.dinoco.de
* 5014 Kerpen 3
* Germany
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies. The author Peter Sprenger
* makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without
* express or implied warranty.
----------------------------------------------------------------------
In the spirit of Peter's effort, I am offering my modification of his
library as FREEWARE. You are free to use it and distribute this
library, but you may charge no fee for it. If you construct another
sound library based on this one, it must be freeware, too. This
restriction does not apply to programs that only use this library to
generate audio output. (ie: if you make a game that uses this
library for sound generation, you can charge all the money
you want for your game.)
**** WARNING ****
Use Varmint's Audio tools at your own risk. This code has not undergone
any sort of rigourous testing and has been found to cause cancer in
laboratory rats.
**************************************************************************/
#include "sound.h"
#define CINTNO 0
#define SLAVEPIC 2
#define RTCINTNO 8
#define WORD unsigned int
#define BYTE unsigned char
#define TD midi_data->track[i] + trkloc[i]
static cardtype CheckHard(void);
static int test_int(void);
static int scan_int(void);
static int FM_Detect(void);
BYTE FM_Status(void);
int DSP_Reset(void);
BYTE DSP_Read(void);
void DSP_Write(BYTE output);
WORD DSP_GetVersion(void);
void SB_SetVect(void);
void SB_RemoveVect(void);
int get_sb_env(void);
int CardCheck(void);
cardtype WhichCard(void);
BYTE int2vect(BYTE intnr);
void enable_int(BYTE nr);
void disable_int(BYTE nr);
void InitT2(void);
void measure(void);
void dma_set(BYTE far *sound_address,WORD len,BYTE channel);
WORD polldma(BYTE channel);
int ReadVarLen(BYTE *data,long int *value);
long int ReadLong(FILE *infile);
int ReadShort(FILE *infile);
void MidiPlayer(void);
int getvoice(VOICE v[],int track,int channel, int note);
static void far interrupt (*orgint)(void) = NULL;
static void far interrupt (*orgtick)(void)= NULL;
static void far interrupt (*orgirqint)(void) = NULL;
static void far (*call_func)(void);
static WORD ioaddr[6]={0x220,0x240,0x210,0x230,0x250,0x260};
static WORD FM_off[9]={0,0x100,0x200,0x800,0x900,0xa00,0x1000,0x1100,0x1200};
static BYTE FM_fnr[12]={0x57,0x6b,0x81,0x98,0xb0,0xca,0xe5,0x02,0x20,0x41,0x63,0x87};
static BYTE FM_key_or[12]={1,1,1,1,1,1,1,2,2,2,2,2};
static BYTE intrx[5]={7,5,2,3,10};
static BYTE FM_key[9],FM_keyscale1[9],FM_keyscale2[9];
static BYTE FM_vol[9] = {0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f};
WORD io_addr,intnr,dma_ch,card_id,fm_addr,fm_left,fm_right,fm_both;
static WORD mue3,mue23,dsp_vers,wh_card,rythm=0xbd00;
volatile BYTE voc_mode,tst_cnt,dma_sb_busy;
static WORD dma_adr[8]= {0x00,0x02,0x04,0x06,0xc0,0xc4,0xc8,0xcc};
static WORD dma_len[8]= {0x01,0x03,0x05,0x07,0xc2,0xc6,0xca,0xce};
static WORD dma_page[8]={0x87,0x83,0x81,0x82,0x8f,0x8b,0x89,0x8a};
WORD dma_bufferlen = 60;
WORD dma_bytesahead =100;
BYTE far *dma_buffer = NULL;
SBERROR sberr = 0;
char *errname[] = {
"Cannot detect FMchip",
"Cannot detect DSP",
"Cannot find an open IRQ",
"Cannot find an open DMA channel",
"Cannot allocate memory for DMA buffer"};
static WORD timer_val,timer_hold,timer_diff,mue999;
static WORD timadd,timsum;
MIDI *midi_data = NULL;
int midi_reset = TRUE;
int midi_on = FALSE;
float midi_callfreq = 1.0;
float midi_usertempo = 2.0;
float midi_tempoadjust = 2.0;
BYTE music_volume = 0x32;
DWORD vclock=0;
int debugnum=0;
WORD DSP_overhead = 0;
int sounds_in_queue = 0;
int sample_rate = 11000;
float interrupt_time_period;
SAMPLE *sounddata[MAXSOUNDS];
DWORD soundpos[MAXSOUNDS];
extern unsigned _stklen = 8000; // The stack is usually only 4K, but
// I was getting stack overflow problems
// with that ammount.
/**************************************************************************
void Go_Varmint(void)
void Dropdead_Varmint(void)
DESCRIPTION: Starts/stops the interrupt routine for Varmint's audio tools
**************************************************************************/
void Go_Varmint(void)
{
SB_SetVect(); // Install the looper
DSP_Write(0x48); // Set DSP for 8bit DMA
DSP_Write((dma_bufferlen) & 0xff); // Write length low byte
DSP_Write((dma_bufferlen) >> 8); // Write length high byte
dma_set(dma_buffer,dma_bufferlen,dma_ch);
DSP_Write(0x1C); // Set DSP for Autoinit 8bit DMA
DSP_Write(DSP_INVOKE_INTR); // Ignition!
}
void Dropdead_Varmint(void)
{
DSP_Write(0xD0); // Halt DMA
SB_RemoveVect();
}
/* --------------- FM Stuff ------------ */
/**************************************************************************
void FM_Write(WORD data)
DESCRIPTION: Writes a byte to the FM chip. The high byte is the
register, the low byte is the data.
**************************************************************************/
void FM_Write(WORD data)
{
asm mov dx,fm_addr; // FM ddress
asm mov ax,data; // register and data into accumulator
asm xchg al,ah; // exchange accumulator bytes
asm out dx,al; // write register address to FM chip
asm mov cx,mue3; // Wait 3 micro seconds
loop1:
asm loop loop1;
asm inc dx; // inc FM address to data write port
asm mov al,ah; // put the data into the low byte of AX
asm out dx,al; // write data to the FM chip
mdelay(mue23); // wait 23 microseconds
}
/**************************************************************************
void FM_Reset()
DESCRIPTION: Resets the FM chip by clearing all the registers then
setting a few appropriate bits.
**************************************************************************/
void FM_Reset(void)
{
WORD i;
for(i = 0; i <= 0xf500 ; i+= 0x100) FM_Write(i);
FM_Write(0x0120); // Turn on Wave form control
FM_Write(0xbdc0); // Set AM and Vibrato to high
}
/**************************************************************************
BYTE FM_Status()
DESCRIPTION: Reads the status byte of the FM chip
**************************************************************************/
BYTE FM_Status(void)
{
asm mov dx,fm_addr;
asm in al,dx;
return(_AL);
}
/**************************************************************************
static int FM_Detect()
DESCRIPTION: Detects the presence of an FM chip
**************************************************************************/
static int FM_Detect(void)
{
FM_Write(0x0100); /* init Test register */
FM_Write(0x0460); /* reset both timer */
FM_Write(0x0480); /* enable interrupts */
if(FM_Status() & 0xe0) return(FALSE);
FM_Write(0x02ff); /* write ffh to timer 1 */
FM_Write(0x0421); /* start timer 1 */
if(fm_addr==0x388) msdelay(21); /* wait 21000 mcs */
else mdelay(mcalc(80)); /* wait at least 80 microsec */
if((FM_Status() & 0xe0)!=0xc0) return(FALSE);
FM_Write(0x0460); /* reset both timer */
FM_Write(0x0480); /* enable interrupts */
return(TRUE);
}
/**************************************************************************
void FM_SetVoice(BYTE voice,BYTE *ins)
DESCRIPTION: Sets the voice from an 11 byte array
BYTE ID
0 Ampmod /vib /envtype /scale rate/ mod freq mult (oper 1)
1 Ampmod /vib /envtype /scale rate/ mod freq mult (oper 2)
2 Key level scaling/ total level (oper 1)
3 Key level scaling/ total level (oper 2)
4 Attack Rate/ Decay rate (oper 1)
5 Attack Rate/ Decay rate (oper 2)
6 Sustain Level/ Release rate (oper 1)
7 Sustain Level/ Release rate (oper 2)
8 Feedback / Algorythm (oper 1&2)
9 Wave Form Select (oper 1)
10 Wave Form Select (oper 2)
**************************************************************************/
void FM_SetVoice(BYTE voice,BYTE *ins)
{
if(voice > 8) return;
FM_keyscale1[voice]=ins[2] & 0xc0; // store key scaling for FM_Vol
FM_keyscale2[voice]=ins[3] & 0xc0;
// Write voice data
FM_Write((0x2000 + FM_off[voice]) | ins[0]);
FM_Write((0x2300 + FM_off[voice]) | ins[1]);
// For the next tow, we want to
// make sure current volume is
// preserved.
FM_Write((0x4000 + FM_off[voice]) | (ins[2] & 0xc0) | FM_vol[voice]);
FM_Write((0x4300 + FM_off[voice]) | (ins[3] & 0xc0) | FM_vol[voice]);
FM_Write((0x6000 + FM_off[voice]) | ins[4]);
FM_Write((0x6300 + FM_off[voice]) | ins[5]);
FM_Write((0x8000 + FM_off[voice]) | ins[6]);
FM_Write((0x8300 + FM_off[voice]) | ins[7]);
FM_Write((0xc000 + voice * 0x100) | ins[8]);
FM_Write((0xE000 + FM_off[voice]) | ins[9]);
FM_Write((0xE300 + FM_off[voice]) | ins[10]);
}
/**************************************************************************
void FM_SetFreq(BYTE voice,int freq)
DESCRIPTION: sets an explicit pseudo frequency (0 - 0xffff)
Note: There is no way to really set a direct frequency on an FM
chip, so I wrote this routine which is based on octaves, so I
imagine it is slightyl non-linear. Still, it is good for
special effects.
**************************************************************************/
void FM_SetFreq(BYTE voice,WORD freq)
{
BYTE highbits,lowbits;
WORD data,frac;
int octave;
octave = (freq / 0x2000); // Extract octtave number (0-7)
// convert remaining fraction into a
// 10 bit value.
frac = ((double)(freq - octave * 0x2000)/(double)0x2000) * 0x157 + 0x157;
highbits = (frac & 0x300) >> 8; // divide fraction into low and high bits
lowbits = frac & 0xff;
data=0xa000+(voice<<8)|lowbits; // store low bits for now
FM_key[voice]=highbits|(octave<<2); // save high bits for Key_on(); (octave 4)
FM_Write(data); // write low bits to FM chip;
}
/**************************************************************************
void FM_SetNote(BYTE voice,BYTE note)
DESCRIPTION: sets the frequency for a chromatic note
**************************************************************************/
void FM_SetNote(BYTE voice,BYTE note)
{
BYTE blk,notex;
WORD data;
// calculate freq number and octave
notex=note-24;
blk=1;
while(notex>=12)
{
notex-=12;
blk++; // octave number
}
data=0xa000+(voice<<8)|FM_fnr[notex];
FM_key[voice]=FM_key_or[notex]|(blk<<2); // save part of the note for Key_on()
FM_Write(data); // write note to the chip
}
/**************************************************************************
void FM_SetVol(BYTE voice,BYTE vol)
DESCRIPTION: The the volume (0-63) for a voice.
**************************************************************************/
void FM_SetVol(BYTE voice,BYTE vol)
{
if (voice >8) return;
FM_vol[voice] = (0x3f - (vol & 0x3f));
FM_Write((0x4000+FM_off[voice]) |FM_vol[voice] | FM_keyscale1[voice]);
FM_Write((0x4300+FM_off[voice]) |FM_vol[voice] | FM_keyscale2[voice]);
}
/* A NOTE ABOUT RYTHM FUNCTIONS:
I've only played around with these functions a little bit. Here are some
things that I've learned:
- only channels 6,7,and 8 are affected by the rythm mode.
- You will need to develop special instrument definitions to get
the rythm instruments to sound right. The most important parameters
in a rythm instrument definition are attack/decay/sustain rates and
the waveform (bytes 9 and 10).
- channels 6,7, and 8 each behave differently in rythm mode:
6 - Instrumental. Sounds like a triangle
7 - White noise. Sounds like a snare drum
8 - High white noise. Sounds like a Cymbal.
- If you want to add white noise effects to your program (Gun shots
engines, etc...) channel 7 in rythm mode is a good source.
- ERIC
*/
/**************************************************************************
void FM_RythmMode(BYTE bool)
DESCRIPTION: Turns on/off rythm mode based on input.
**************************************************************************/
void FM_RythmMode(BYTE bool)
{
WORD data;
if(bool) data=0xbde0;
else data=0xbdc0;
rythm=data;
FM_Write(data);
}
/**************************************************************************
void FM_RythmOn(BYTE inst)
DESCRIPTION: Turns on a Specified rythm instrument. You should use these
definitions:
FM_HIHAT
FM_TOPCYM
FM_TOMTOM
FM_SNARE
FM_BASS
**************************************************************************/
void FM_RythmOn(BYTE inst)
{
rythm|=inst;
FM_Write(rythm);
}
/**************************************************************************
void FM_RythmOff(BYTE inst)
DESCRIPTION: Turns off a Specified rythm instrument. You should use these
definitions:
FM_HIHAT
FM_TOPCYM
FM_TOMTOM
FM_SNARE
FM_BASS
**************************************************************************/
void FM_RythmOff(BYTE inst)
{
rythm&=(~inst);
FM_Write(rythm);
}
/**************************************************************************
void FM_KeyOn(BYTE voice)
DESCRIPTION: Turn on an FM voice. (actually it initiates it.)
**************************************************************************/
void FM_KeyOn(BYTE voice)
{
WORD data;
if(voice > 8) return;
data=0xb000+(voice<<8); // set write address
data |= FM_key[voice]|0x20; // set key on bit and frequency
FM_Write(data);
}
/**************************************************************************
void FM_KeyOff(BYTE voice)
DESCRIPTION: Turn off an FM voice
**************************************************************************/
void FM_KeyOff(BYTE voice)
{
WORD data;
if(voice > 8) return;
data=0xb000+(voice<<8); // set address
data |= FM_key[voice]; // preserve frequency data
FM_Write(data);
// working.
}
/* --------------- DSP Stuff ------------ */
/**************************************************************************
int DSP_Reset()
DESCRIPTION: Resets the DSP
**************************************************************************/
int DSP_Reset(void)
{
int i;
asm mov dx,io_addr; // load address for dsp reset
asm add dx,DSP_RESET;
asm mov al,1
asm out dx,al; // Send a 1 to the DSP
mdelay(mcalc(mue3)); // Wait three micro seconds
asm mov dx,io_addr; // load address again
asm add dx,DSP_RESET;
asm mov al,0
asm out dx,al; // send a 0 to the DSP
for(i=0;i<50;i++) // DSP should send back an 0xaa
{
mdelay(mcalc(mue3));
if(DSP_Read()==0xaa) return(TRUE);
}
return(FALSE);
}
/**************************************************************************
BYTE DSP_Read()
DESCRIPTION: reads a byte from the dsp
**************************************************************************/
BYTE DSP_Read(void)
{
asm mov dx,io_addr; // load address to read status byte
asm add dx,DSP_RSTATUS;
loop:
asm in al,dx; // read until high bit is set
asm test al,0x80;
asm jz loop
asm mov dx,io_addr; // read byte from output address
asm add dx,DSP_READ;
asm in al,dx;
return(_AL);
}
/**************************************************************************
void DSP_Write(BYTE output)
DESCRIPTION: Writes a byte to the DSP
**************************************************************************/
void DSP_Write(BYTE output)
{
asm mov dx,io_addr; // dx holds the port address
asm add dx,DSP_WSTATUS;
loop: // wait for bit 7 to clear
asm in al,dx;
asm test al,0x80;
asm jnz loop
// write our bute!
asm mov dx,io_addr;
asm add dx,DSP_WRITE;
asm mov al,output
asm out dx,al;
}
/**************************************************************************
int get_sb_env()
DESCRIPTION: Get sound blaster information from the environment
variable "BLASTER"
**************************************************************************/
int get_sb_env(void)
{
char *str;
int i;
str=getenv("BLASTER");
if(!str) return(FALSE); // no blaster variable? go home
// Convert string to upper case
for(i = 0 ; i < strlen(str); i++) *(str+i) = toupper(*(str+i));
// pick apart variable for info.
// Io address
for(i = 0; *(str+i) != 0 && *(str + i) != 'A'; i++);
if(*(str+i) == 0) return(FALSE);
sscanf(str+i+1,"%x",&io_addr);
if(io_addr<0x210 || io_addr>0x260) return (FALSE);
// Dma channel number
for(i = 0; *(str+i) != 0 && *(str + i) != 'D'; i++);
if(*(str+i) == 0) return(FALSE);
sscanf(str+i+1,"%d",&dma_ch);
if(dma_ch > 7) return(FALSE); // only 0-7 allowed
// IRQ port number (?)
for(i = 0; *(str+i) != 0 && *(str + i) != 'I'; i++);
if(*(str+i) == 0) return(FALSE);
sscanf(str+i+1,"%d",&intnr);
if(intnr < 2 || intnr > 10) return (FALSE);
// card_id
for(i = 0; *(str+i) != 0 && *(str + i) != 'T'; i++);
if(*(str+i) == 0) return(FALSE);
sscanf(str+i+1,"%d",&card_id);
return(TRUE);
}
/**************************************************************************
WORD DSP_GetVersion()
DESCRIPTION: Get the version number of the DSP
**************************************************************************/
WORD DSP_GetVersion(void)
{
DSP_Write(DSP_GET_VERS);
return((WORD)DSP_Read()*256+DSP_Read());
}
/* --------------- Misc. Stuff ------------ */
/**************************************************************************
int CardCheck()
DESCRIPTION: Check for both FM chip and DSP
**************************************************************************/
int CardCheck(void)
{
int ret=0;
if(FM_Detect()) ret|=FM_DETECT;
if(DSP_Reset()) ret|=DSP_DETECT;
return(ret);
}
/**************************************************************************
static void far interrupt testn_int()
DESCRIPTION: Function stored as an interrupt to test various interrrupt
vectors by test_int()
**************************************************************************/
static void far interrupt testn_int(void)
{
tst_cnt++;
asm mov dx,io_addr;
asm add dx,DSP_RSTATUS;
asm in al,dx; /* Ack DSP interrupt */
asm mov al,0x20;
asm out 0x20,al; /* set EOI */
asm mov bx,intnr
asm cmp bx,7
asm jbe end
asm out 0xa0,al; /* set EOI */
end:
}
/**************************************************************************
static int test_int()
DESCRIPTION: This function is used by scan_int() to test interrupt
stuff. It installs a test interrupt in the
requested spot (intnr) then sees if the DSP can
use it.
**************************************************************************/
static int test_int(void)
{
int i;
BYTE int1,int2;
orgint=getvect(int2vect(intnr));
asm in al,0x21; /* save org master intr settings */
asm mov int1,al;
asm in al,0xa1; /* save org slave intr settings */
asm mov int2,al;
asm mov al,0xfe;
asm cli;
asm out 0x21,al; /* disable ALL intr (except timer) */
asm mov al,0xff
asm out 0xa1,al;
asm sti;
tst_cnt=0; // reset our test interrupt counter.
enable_int(intnr); // put in our test interrupt
setvect(int2vect(intnr),testn_int);
DSP_Write(DSP_INVOKE_INTR); /* still magic -- make DSP interrupt? */
for(i=0;i<30000;i++) if(tst_cnt) break;
asm cli;
asm mov al,int1;
asm out 0x21,al; /* restore org master intr */
asm mov al,int2; /* restore org slave intr */
asm out 0xa1,al;
asm sti;
setvect(int2vect(intnr),orgint);
if(i==30000) return(FALSE);
else return(TRUE);
}
/**************************************************************************
static int scan_int()
DESCRIPTION: This makes sure that the interrupt number picked by the
IRQ specification is a good choice.
**************************************************************************/
static int scan_int(void)
{
int i;
if(test_int()) return(intnr); // Original choice good?
for(i=0;i<5;i++) // Try our five best guesses
{
intnr=intrx[i];
if(test_int()) return(i);
}
return(0);
}
/**************************************************************************
static cardtype CheckHard()
DESCRIPTION: Checks hardware for DSP and FM chip
**************************************************************************/
static cardtype CheckHard(void)
{
int ret;
ret=DSP_Reset();
if(ret)
{
if(!scan_int()) { // Scan IRQ's
sberr= irqerr;
return(none);
}
fm_addr=io_addr+FM_BOTH_OFF;
if(!FM_Detect()) {
sberr = fmerr;
return(none); /* no fm? -> damaged! */
}
// SBPro checking here. Not too critical
/* fm_both=fm_addr;
fm_addr=io_addr+FM_RIGHT_OFF;
fm_right=fm_addr;
ret3=FM_Detect();
fm_addr=fm_both;
if(ret3)
{
wh_card=sbpro;
fm_left=io_addr+FM_LEFT_OFF;
}
else wh_card=sb20; */
wh_card = sb20;
return(wh_card);
}
sberr = nodsperr;
return(none);
}
/**************************************************************************
cardtype WhichCard()
DESCRIPTION: Calls various functions to make sure you've
got a Sound Blaster
**************************************************************************/
cardtype WhichCard(void)
{
cardtype cret;
int i;
if(get_sb_env()) cret=CheckHard(); // grab environment variable
if(cret!=nodsp) return(cret); // If dsp is there, then go home
intnr=7;
for(i=0;i<6;i++) // scan around for a better io address
{
io_addr=ioaddr[i];
cret=CheckHard();
if(cret!=nodsp) return(cret);
}
return(none); // Uh oh.
}
/**************************************************************************
int SB_Setup()
DESCRIPTION: Sets up the sound blaster for action. This is the only
function a programmer should really use. Most of the
nitty gritty is handled internally.
**************************************************************************/
int SB_Setup(void)
{
int i;
InitT2(); /* init Timer 2 */
measure(); /* time loop factor */
mue3=mcalc(3) ; /* calc val for 3 micro sec delay */
mue23=mcalc(23) ; /* calc val for 23 micro sec delay */
WhichCard();
if(wh_card==none) return(FALSE);
if(wh_card==sb20 || wh_card==sbpro)
{ // Get DSP ready
dsp_vers=DSP_GetVersion();
DSP_Write(DSP_SPKR_ON);
}
// Allocate space for Mixing buffer
dma_buffer = (BYTE far *)farmalloc(dma_bufferlen+5);
if(!dma_buffer) {
sberr = nomem;
return(FALSE);
}
// Clear the buffer
for(i = 0; i < dma_bufferlen+4; i++) {
*(dma_buffer+i) = 0;
}
SetRate(11000); // Set the sample rate
return(TRUE);
}
/**************************************************************************
DWORD far2long(char far *adr)
DESCRIPTION: This is used by dma_set to convert a regular far address
to a 20 bit flat address.
**************************************************************************/
DWORD far2long(char far *adr)
{
return(((DWORD)FP_SEG(adr)<<4)+FP_OFF(adr));
}
/**************************************************************************
void SetRate(WORD rate)
DESCRIPTION: Sets the sample rate (specified in hz)
**************************************************************************/
void SetRate(WORD rate)
{
DWORD val;
if(rate<4000) return; // Calculate number for the sound card
val=256-1000000L/rate;
DSP_Write(DSP_SAMPLE_RATE);
DSP_Write((BYTE)val);
sample_rate = rate; // FYI
}
/**************************************************************************
WORD dma_set(DWORD adrl,WORD len,int channel)
DESCRIPTION: This programs the DMA controller to start a single pass
output transfer.
(Draeden of VLA has provided some good information for
DMA programming in his INTRO to DMA document)
**************************************************************************/
void dma_set(BYTE far *sound_address,WORD len,BYTE channel)
{
WORD adr;
DWORD adrl;
BYTE page;
adrl = far2long(sound_address); // convert address to 20 bit format
adr=(WORD)adrl; // extract page address
page=(BYTE)(adrl>>16); // extract page number
// PREPARE DMA.
// (Channels 0-3 have different command
// ports than 4-7.)
if(channel < 4) { // channels 0-3?
asm {
// SET THE CHANNEL MASK BIT
mov al,04 // set 3rd bit
add al,channel
out 0x0a,al // write the channel
mov al,0
out 0x0c,al // Clear the byte pointer
mov al,0x58 // Read mode (was 48)
add al,channel
out 0x0b,al // set the mode
}
}
else { // channels 4-7?
asm {
// SET THE CHANNEL MASK BIT
mov al,channel // (no need to set third bit)
out 0xd4,al // write the channel
mov al,0
out 0xd8,al // Clear the byte pointer
mov al,0x58 // Read mode
add al,channel
out 0xd6,al // set the mode
}
}
// OK. Now the transfer info
// WRITE THE ADDRESS OF THE DATA
_DX = dma_adr[channel]; // Set the address port
asm {
mov ax,adr
out dx,al // Write address low byte
mov al,ah
out dx,al // write address high byte
}
_DX = dma_page[channel]; // Set the page port
asm {
mov al,page
out dx,al // Write the page byte
}
// WRITE THE LENGTH OF THE DATA
_DX = dma_len[channel]; // Set the length port
asm {
mov ax,len
out dx,al // Write length low byte
mov al,ah
out dx,al // write length high byte
}
// WRITE THE PAGE LOCATION OF THE DATA
// CLEAR THE CHANNEL MASK BIT
if(channel < 4) {
asm {
mov al,channel // (mask bit already clear)
out 0x0a,al // write the channel
}
}
else {
asm {
mov al,channel
and al,0x03 // Clear the mask bit
out 0x0a,al // write the channel
}
}
}
/**************************************************************************
void polldma(BYTE channel)
DESCRIPTION: This function poles the DMA controller to find out how many
bytes are left in the current transfer.
As of version 0.4, this function is no long used, but
I thought it might be useful to someone else, so I've only
commented it out.
**************************************************************************/
/*WORD polldma(BYTE channel)
{
BYTE low1,high1,low2,high2;
disable_int(intnr); // Turn off the interrupt so we don't get
// caught with our pants down.
asm {
mov dx,0x0c // Flip the master reset switch
mov al,0
out dx,al
}
_DX = dma_len[channel]; // Load in the counter address
// read position twice, becasue sometimes
// there is a problem
asm{
in al,dx // read the low byte first
mov low1,al
in al,dx // read the high byte next
mov high1,al
in al,dx // read the low byte first
mov low2,al
in al,dx // read the high byte next
mov high2,al
}
enable_int(intnr); // Done, so we'll put the interrupt back.
// High bytes the same? Use second reading
if(high1 == high2)return((WORD)high2*256+low2);
// else the First reading is accurate
return(high1*256+low1);
}*/
/**************************************************************************
static void far interrupt sb_int()
DESCRIPTION: This is the sound Blaster interrupt that is to be
called at the end of DMA transfer. This software is
set up for a continual DMA transfer, so all it does
is have the DMA transfer start all over again.
**************************************************************************/
static void far interrupt sb_int(void)
{
int d,i,j;
static long int midi_count = 100;
static int dma_pos=0;
if(DSP_overhead) timer_on(); // start timer to measure DSP overhead
// SAMPLE MIXER
// Write data to the dma buffer
for(dma_pos = 0; dma_pos < dma_bufferlen+1; dma_pos++) {
d = 128; // reset data holder
for(i = 0; i < sounds_in_queue; i++) {
d += *(sounddata[i]++); // accumulate data
soundpos[i]--; // move to next byte
if(!soundpos[i]) { // Sound done?
// Clear the slot and scoot sounds left
for(j = i; j < sounds_in_queue;j++) {
sounddata[j] = sounddata[j+1];
soundpos[j] = soundpos[j+1];
}
sounds_in_queue--;
}
}
if(d>255) d = 255; // clip the output byte
if(d<0) d = 0;
*(dma_buffer + dma_pos) = d; // Write data to the buffer
}
*(dma_buffer) = d; // This prevents the popping sound
// That can happen when the buffer is
// suddenly reset
// acknowledge DSP interrupt
asm mov dx,io_addr;
asm add dx,DSP_RSTATUS;
asm in al,dx;
// reset interrupt
asm mov al,0x20;
asm out 0x20,al; /* set EOI */
asm mov bx,intnr
asm cmp bx,7
asm jbe end
asm out 0xa0,al; /* set EOI */
end:
// Varmint's system clock
vclock ++;
// MIDI stuff first
midi_count-= 100;
if(midi_count < 100) {
MidiPlayer(); // Call a midi player
midi_count += midi_callfreq * midi_usertempo*100.0; // reset counter
debugnum =midi_usertempo * 100;
}
if(DSP_overhead) DSP_overhead = timer_off();// How long did this take?
}
/**************************************************************************
void SB_SetVect()
DESCRIPTION: Installs the DMA interrupt vector. This makes it so that
sb_int() is called whenever a DMA transfer is finished
**************************************************************************/
void SB_SetVect(void)
{
orgirqint=getvect(int2vect(intnr));
setvect(int2vect(intnr),sb_int); /* set vector to our routine */
enable_int(intnr); /* enable sb interrupt */
}
/**************************************************************************
void SB_RemoveVect()
DESCRIPTION: Removes the DMA interrupt vector
**************************************************************************/
void SB_RemoveVect(void)
{
disable_int(intnr); /* disable sb interrupt */
setvect(int2vect(intnr),orgirqint); /* restore org intr vector */
}
/* --------------------------------------------------- */
/* timerX routines are following
These routines are for highly accurate time measurements
*/
/**************************************************************************
void InitT2()
DESCRIPTION: Initializes speaker timer for timing operations.
**************************************************************************/
void InitT2(void)
{
asm in al,0x61 /* no signal on speaker! */
asm and al,0xfd
asm or al,1
asm out 0x61,al
asm mov al,0xb4 /* program timer 2 with modus 2 */
asm out 0x43,al /* and counter value of 0 (2^16)*/
asm mov al,0
asm out 0x42,al
asm out 0x42,al
}
/**************************************************************************
void timer_on()
DESCRIPTION: Turns on timer counter for a time measurement
**************************************************************************/
void timer_on(void)
{
asm mov al,0x80; /* latch timer 2 */
asm out 0x43,al; /* save value in timer_hold */
asm in al,0x42;
asm mov bl,al;
asm in al,0x42;
asm mov bh,al;
asm mov timer_hold,bx;
}
/**************************************************************************
WORD timer_off()
DESCRIPTION: Turns off time and reports clicks elapsed. Note that this
timer is so quick that it is wraps after only 56
milliseconds. If you want to timer longer stuff, I suggest
using the global variable vclock. It's tick frequency is
sample_rate / dma_bufferlen.
**************************************************************************/
WORD timer_off(void)
{
asm mov al,0x80; /* latch timer 2 */
asm out 0x43,al;
asm in al,0x42;
asm mov ah,al;
asm in al,0x42;
asm xchg ah,al;
asm mov bx,timer_hold;
asm sub ax,bx;
asm neg ax;
asm mov timer_diff,ax; /* calc timer_hold - ax to timer_diff */
return(_AX);
}
/**************************************************************************
WORD to_micro(WORD clk)
DESCRIPTION: Converts clock ticks number to microsecs
**************************************************************************/
WORD to_micro(WORD clk)
{
return(clk*838/1000);
}
/**************************************************************************
void clkdelay(WORD clicks)
DESCRIPTION: Wait specified number of clock ticks
**************************************************************************/
void clkdelay(WORD clicks)
{
asm mov al,0x80; /* latch timer 2 */
asm out 0x43,al; /* save value in bx */
asm in al,0x42;
asm mov bl,al;
asm in al,0x42;
asm mov bh,al;
loop:
asm mov al,0x80;
asm out 0x43,al;
asm in al,0x42;
asm mov ah,al;
asm in al,0x42;
asm xchg ah,al;
asm sub ax,bx;
asm neg ax;
asm cmp ax,clicks; /* leave routine after click CLK's */
asm jle loop;
}
/**************************************************************************
void measure()
DESCRIPTION: measures a standard delay loop for other delay functions
**************************************************************************/
void measure(void)
{
timer_on();
asm cli
asm mov cx,10000 /* internal test loop */
loop1:
asm loop loop1
timer_off();
asm sti
timer_val=timer_diff;
mue999=mcalc(999); /* calc for msdelay */
}
/**************************************************************************
void mdelay(WORD delay)
DESCRIPTION: Very tiny delay
**************************************************************************/
void mdelay(WORD delay)
{
asm mov cx,delay
loop1:
asm loop loop1
}
/**************************************************************************
void _saveregs msdelay(WORD delay)
DESCRIPTION: Millisec delay. When using this library, you should use this
delay for millisecond delays instead of the delay
functions that comes with turbo C.
**************************************************************************/
void _saveregs msdelay(WORD delay)
{
WORD i;
for(i=0;i<delay;i++) mdelay(mue999); /* only 999 cause of fnctn call */
}
/**************************************************************************
WORD mcalc(WORD micro)
DESCRIPTION: Calculates number of ticks to send to mdelay for a specified
number of microseconds.
**************************************************************************/
WORD mcalc(WORD micro)
{
return(WORD)((long)micro*10000L/timer_val*1000/838);
}
/********************************
TIMER 0 STUFF
*********************************/
/* --------------------------------
CAUTION: These routines can cause a lot of headaches while debugging.
If you set your own interrupt and then stop the program before you call
Remove_Timer0(), you'd better reboot your computer, because very
unpredictable things will happen if Install_Timer0() is called again.
My suggestion is to get your interrupt working and then comment out the
Timer0 routines until the rest of the program is written and debugged.
- Eric */
/**************************************************************************
static void interrupt timerint()
DESCRIPTION: THis is the actual interrupt function stored in the timer
0 slot (int 08). This calls the old int08 function
at proper intervals as well as the user specified function
**************************************************************************/
static void interrupt timerint(void)
{
timer_on(); // set timer for overhead calculation.
call_func(); // user specified function
DSP_overhead = timer_off(); // Get the time (in clicks) it took
// Now let's do some fancy counting so
// we can call the system clock at the
// right moments.
if(timsum<100)
{
timsum+=timadd;
orgtick();
}
else
{
asm mov al,0x20;
asm out 0x20,al;
}
timsum-=100; // decrement our special timer
}
/**************************************************************************
void Install_Timer0(WORD period,void far (*func)())
DESCRIPTION: This sets up timer0 to call your function at the specified
period.
**************************************************************************/
void Install_Timer0(WORD period,void far (*func)(void))
{
if(!func) return; /* no valid func ptr */
call_func=func;
timadd= (WORD)(6553600L/period); // counting seed for timerint()
timsum=0; // start counter at 0
interrupt_time_period = period / 1193180.0 ;
asm mov al,0x36 /* program timer 0 with modus 3 */
asm out 0x43,al /* and counter value of period */
asm mov ax,period
asm out 0x40,al
asm mov al,ah
asm out 0x40,al
orgtick= getvect(8); // Remember the old interrupt
setvect(8,timerint); // put in a new one.
}
/**************************************************************************
void Remove_Timer0()
DESCRIPTION: Removes your goofy interrupt, 'cause we didn't want
it anyway! :P
**************************************************************************/
void Remove_Timer0(void)
{
if(!orgtick) return; // Must have called Install_Timer0 first
asm mov al,0x36 /* program timer 0 with modus 3 */
asm out 0x43,al /* and counter value of 0 (2^16)*/
asm mov al,0
asm out 0x40,al
asm out 0x40,al
setvect(8,orgtick); // put back original vector
}
/**************************************************************************
BYTE int2vect(BYTE intnr)
DESCRIPTION: This function converts a PIC irq number to a true
interrupt vector number. For PC's with a 286 or greater,
irq's 0-7 refer to interrupts 0x08 - 0x0F and
irq's 8-15 refer to interrupts 0x70 - 0x77.
**************************************************************************/
BYTE int2vect(BYTE intnr)
{
if(intnr>7) return(intnr + 0x68);
else return(intnr+8);
}
/**************************************************************************
void enable_int(BYTE nr)
DESCRIPTION: Enables an IRQ interrupt using the Programmable
interrupt controller (PIC)
**************************************************************************/
void enable_int(BYTE nr)
{
if(nr>7) /* use 2nd intr controller? */
{
asm in al,0xa1; // Read the PIC status from 0xa1
asm mov cl,nr; // load the interrupt number in the counter
asm sub cl,8 // subract 8 to get the bit location right
asm mov bl,1; // load a 1 to bl
/* calc correct mask */
asm shl bl,cl; // Bitshift left bl by cl
asm not bl; // calculate the compliment
asm and al,bl; // leave a hole
asm out 0xa1,al; // write the result to the port
}
else
{
asm in al,0x21;
asm mov cl,nr;
asm mov bl,1;
asm shl bl,cl; /* calc correct mask */
asm not bl;
asm and al,bl;
asm out 0x21,al;
}
}
/**************************************************************************
void disable_int(BYTE nr)
DESCRIPTION: Disables an IRQ interrupt using the Programmable
interrupt controller.
**************************************************************************/
void disable_int(BYTE nr)
{
if(nr>7)
{
asm in al,0xa1; /* use 2nd intr controller? */
asm mov cl,nr;
asm sub cl,8
asm mov bl,1;
asm shl bl,cl; /* calc correct mask */
asm or al,bl;
asm out 0xa1,al;
}
else
{
asm in al,0x21;
asm mov cl,nr;
asm mov bl,1;
asm shl bl,cl; /* calc correct mask */
asm or al,bl;
asm out 0x21,al;
}
}
/**************************************************************************
int getvoice(VOICE v[],int track,int channel, int note)
DESCRIPTION: Find the first matching voice (or first inactive voice if
a match is not found). This function is used by the midi
routine as an interface to get FM voices.
**************************************************************************/
int getvoice(VOICE v[],int track,int channel, int note)
{
int i;
for(i = 0; i < 9; i++) { // find matching active note
if(v[i].active) {
if(v[i].owner_track == track &&
v[i].owner_channel == channel &&
v[i].note == note) return(i);
}
}
// no note, so find first inactive voice
for(i = 0; i < 9; i++) {
if(!v[i].active) return(i);
}
// no available voices... error
return -1;
}
/**************************************************************************
MidiPlayer()
DESCRIPTION: Routine for playing midi files. THis is designed to be
called from a timer interrupt. To use, set these values
in this order:
midi_data (must point to a filled MIDI structure.)
midi_reset = TRUE;
midi_on = TRUE;
The interrupt should pick up from there.
It is easy to add functionality to this routine. I've
already included code to flag a wide variety of MIDI
events, so all you have to do is add your own code under
the point an event is flagged. I've left a bunch of
commented print statements in to help make the code
more readable and provide cues for accessing the data.
*** WARNING ***
If you add your own code here, make sure that it doesn't
take more than a few milliseconds to execute. If
MidiPlayer() is called again by the interrupt before your
code is done, your whole program will probably crash.
**************************************************************************/
void MidiPlayer(void)
{
static VOICE v[9]; // Nine FM voices
VOICE vh[9]; // waiting list
int vhold = 0;
static int i,j,live_tracks,vidx;
static int divisions = 96,ms_per_div=5000;
static BYTE event,ev,ch,b1,b2,etype,track_on[16];
static last_ev[16];
static long int trkloc[16],itmr,length,l2;
static float tmr[16];
static char tdata[256];
// static float beat_ratio = 1.0;
if(!midi_data) { // must have data to play!
midi_on = FALSE;
return;
}
if(midi_reset) { // Reset? zero track pointers and timers
for(i = 0; i < 16; i++ ) {
trkloc[i] = 1; // no need to read first time offset
tmr[i] = 0; // all timers start at zero
track_on[i] = TRUE;
if(i < 9) v[i].active = 0; // unreserve all voices
last_ev[i] = 0x80; // set last event to note off
}
midi_reset = 0; // clear midi reset flag
live_tracks = midi_data->num_tracks;// set number of active tracks so
// we know when to stop.
divisions = midi_data->divisions; // ticks per quarter note
if(divisions < 0) divisions = -divisions; // some midi files have
// negative division values
}
if(!midi_on) return; // logical switch for midi on/off
for(i = 0 ; i < midi_data->num_tracks; i++) { // loop over tracks
while(tmr[i] <= 0) { // Process while timer is 0;
event = *(TD); // get next event (TD is a macro)
trkloc[i]++; // advance track location pointer
if(event == 0xFF) { // META event?
etype = *(TD);
trkloc[i] ++;
trkloc[i] += ReadVarLen(TD,&length); // read length of meta event
// grab any text data for text events
for(j = 0; j < length; j++) tdata[j] = *(TD + j);
tdata[j] = 0;
switch(etype) {
case 0x00:
j = *(TD)*256 + *(TD+1);
//printf("[%d] SEQUENCE NUMBER (%d)\n",i,j);
break;
case 0x01:
//printf("[%d] TEXT EVENT (%s)\n",i,tdata);
break;
case 0x02:
//printf("[%d] COPYWRITE EVENT (%s)\n",i,tdata);
break;
case 0x03:
//printf("[%d] TRACK NAME EVENT (%s)\n",i,tdata);
break;
case 0x04:
//printf("[%d] INSTRUMENT NAME EVENT (%s)\n",i,tdata);
break;
case 0x05:
//printf("[%d] LYRIC EVENT (%s)\n",i,tdata);
break;
case 0x06:
//printf("[%d] MARKER EVENT (%s)\n",i,tdata);
break;
case 0x07:
//printf("[%d] CUE EVENT (%s)\n",i,tdata);
break;
case 0x2f: // End of track
//printf("[%d] END OF TRACK\n",i);
tmr[i] = MAXFLOAT; // set timer to highest value
track_on[i] = FALSE; // turn off track
live_tracks--; // decrement track counter
if(live_tracks == 0) { // last track? Turn off midi!
midi_on = FALSE;
midi_reset = TRUE; // Make sure we start over
return;
}
break;
case 0x51: // TEMPO event (microsecs per 1/4 note)
l2 = *(TD) * 0x10000L + *(TD+1) * 0x100 + *(TD+2);
//printf("[%d] TEMPO EVENT (%ld)\n",i,l2);
ms_per_div = (int)(l2/divisions);
// Convert number to a counter used
// by an 183 Hhz interrupt.
midi_callfreq = ms_per_div/5454.0;
break;
case 0x58:
//printf("[%d] TIME SIG EVENT (%X,%X,%X,%X)\n",i,
// *(TD),*(TD+1),*(TD+2),*(TD+3));
break;
case 0x59:
//printf("[%d] KEY SIG EVENT (%X,%X)\n",i,*(TD),*(TD+1));
break;
case 0x7F:
//printf("[%d] SEQUENCER DATA EVENT\n",i);
break;
default:
//printf("[%d] *** undefined event *** (%X,type: %X,length %ld)\n",i,event,etype,length);
break;
}
trkloc[i] += length;
}
else if(event == 0xF0 || event == 0xF7) { // sysex event
trkloc[i] += ReadVarLen(TD,&length);
//printf("Sysex type 1 [length: %ld]\n",length);
trkloc[i] += length;
}
else { // PROCESS MIDI EVENTS
if(!(event & 0x80)) { // top bit Not set? Running status!
b1 = event; // b1 = note (usually)
b2 = *(TD + 1); // b2 = volume? (usually)
event = last_ev[i]; // use last event
//printf("Running status >>");
//for(j = 0; j < 9; j++) printf("%d",v[j].active);
//printf("\n");
trkloc[i] --; // one less byte for running status.
}
else { // Else it was a regular event
last_ev[i] = event; // set to last event
b1 = *(TD); // get next two bytes
b2 = *(TD+1);
}
ev = event & 0xF0; // strip lower four bits
ch = event & 0x0f; // channel
vidx = getvoice(v,i,ch,b1); // Get a voice index
switch(ev) {
case 0x80: // Note off
//printf("[%d] Note off (%d,%d)",i,b1,b2);
trkloc[i] += 2;
if(vidx > -1) { // If a matching voice was found,
// kill it.
FM_KeyOff(vidx);
FM_SetVol(vidx,0);
v[vidx].active = FALSE;
}
break;
case 0x90: // Note On
//printf("[%d] Note on (%X,%d,%d)",i,event,b1,b2);
trkloc[i] += 2;
if(vidx > -1) { // Voice found?
if(v[vidx].active) { // already active? Turn it off.
v[vidx].active = FALSE;
FM_KeyOff(vidx);
FM_SetVol(vidx,0);
}
else { // Wasn't active? Turn it on.
v[vidx].owner_track = i;
v[vidx].owner_channel = ch;
v[vidx].note = b1;
v[vidx].volume = b2;
v[vidx].active = TRUE;
FM_SetNote(vidx,b1);
FM_SetVol(vidx,music_volume);
FM_KeyOn(vidx);
}
}
else { // There might be space later
// store our note
vh[vhold].owner_track = i;
vh[vhold].owner_channel = ch;
vh[vhold].note = b1;
vh[vhold].volume = b2;
vhold ++;
if(vhold >8) vhold = 8; // Only nine hold notes considered
}
break;
case 0xA0: // Key pressure
//printf("[%d] Note presure (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
break;
case 0xB0: // Control CHange
//printf("[%d] Control Change (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
break;
case 0xC0: // Program change
//printf("[%d] Program change (%d)\n",i,b1);
trkloc[i] += 1;
break;
case 0xD0: // Channel Pressure
//printf("[%d] Channel Pressure (%d,%d)\n",i,b1);
trkloc[i] += 1;
break;
case 0xE0: // Pitch wheel change
//printf("[%d] Pitch change (%d,%d)\n",i,b1,b2);
trkloc[i] += 2;
break;
default: // Uh-OH
//printf("MIDI ERROR (F0 midi command)\n");
midi_on = FALSE;
return;
}
}
// read next time offset
if(track_on[i]) {
trkloc[i] += ReadVarLen(TD,&itmr);
tmr[i] += itmr;
//printf(" T: %ld\n",tmr[i]);
}
}
tmr[i]-= 1.0 * midi_tempoadjust; // decrement timer
}
// Since there is a limited number
// of FM voices, some notes do
// not get voiced. This next
// section takes a list of
// unallocated notes and tries
// to find a spot for them.
while(vhold) {
vhold--; // go to next note
for(i = 0; i < 9; i++) { // loop through FM voices
if(!v[i].active) { // found empty one? set the note!
v[i].owner_track = vh[vhold].owner_track;
v[i].owner_channel = vh[vhold].owner_channel;
v[i].note = vh[vhold].note;
v[i].volume = vh[vhold].volume;
v[i].active = TRUE;
FM_SetNote(i,b1);
FM_SetVol(i,music_volume);
FM_KeyOn(i);
break;
}
}
if(i == 9) vhold = 0; // List full? forget about other notes
}
}
/**************************************************************************
ReadMidi(char *filename, MIDI *mdata, char *errstring)
DESCRIPTION: Reads a midi file and stores it to a MIDI data structure
INPUTS:
filename Pointer to full midi filename
midipoint Indirect pointer to empty midi pointer
errstring Pointer to a pre-allocated string.
Outputs:
returns 0 if successful.
On error, it returns a number and fills errstring with the
error message.
**************************************************************************/
int ReadMidi(char *filename, MIDI **midipoint, char *errstring)
{
int i;
FILE *input;
char sdata[256];
long int lidata;
int idata,lread;
MIDI *mdata;
input = fopen(filename,"rb"); // open a midi file
if(!input) {
sprintf(errstring,"cannot open %s",filename);
return(1);
}
// Read the header
fread(sdata,1,4,input); // midi id there?
sdata[4] = 0;
if(strcmp(sdata,"MThd")) {
sprintf(errstring,"Not a midi file.");
fclose(input);
return(2);
}
// printf("Chunk type: %s\n",sdata);
lidata = ReadLong(input); // length of remaining header chunk?
// printf("Chunk length: %ld\n",lidata);
if(lidata > 250) {
sprintf(errstring,"Header chunk has a weird length");
exit(0);
}
mdata = (MIDI *)malloc(sizeof(MIDI)); // make room for music!
if(!mdata) {
sprintf(errstring,"Out of memory.");
fclose(input);
return(3);
}
*midipoint = mdata; // Assign our pointer
idata = ReadShort(input); // Format
//printf("Format: %d\n",idata);
if(idata != 0 && idata != 1) {
sprintf(errstring,"Unrecognized MIDI format");
fclose(input);
return(4);
}
mdata->format = idata;
idata = ReadShort(input); // number of tracks
//printf("# of Tracks: %d\n",idata);
if(idata < 1 || idata > 16) {
sprintf(errstring,"Bad number of tracks [%d]",idata);
fclose(input);
return(5);
}
mdata->num_tracks = idata;
idata = ReadShort(input); // division number (tempo)
//printf("1/4 note division: %d\n",idata);
mdata->divisions = abs(idata);
// Read individual track data
for(i = 0; i < mdata->num_tracks; i++) {
fread(sdata,1,4,input); // midi track id there?
sdata[4] = 0;
if(strcmp(sdata,"MTrk")) {
sprintf(errstring,"Error reading track #%d",i);
fclose(input);
return(6);
}
//printf("Chunk type: %s\n",sdata);
lidata = ReadLong(input); // length of remaining track chunk?
//printf("Chunk length: %ld\n",lidata);
// Allocate space for track
mdata->track[i] = (BYTE *)malloc(lidata);
if(!mdata->track[i]) {
sprintf(errstring,"Out of memory.");
fclose(input);
return(3);
}
// read in entire track
lread = fread(mdata->track[i],1,lidata,input);
if(lread < lidata) {
sprintf(errstring,"Premature end of midi file [track %d]",i);
fclose(input);
return(7);
}
}
fclose(input);
return 0;
}
/**************************************************************************
int ReadVarLen(char *data,long int *value)
DESCRIPTION: Reads a variable length long interger from data string
**************************************************************************/
int ReadVarLen(BYTE *data,long int *value)
{
int i=0;
BYTE c;
if ((*value = *(data + i)) & 0x80) {
*value &= 0x7f;
do {
i++;
*value = (*value << 7) + ((c = *(data +i)) & 0x7f);
} while (c & 0x80);
}
return(i+1); // return number of bytes read
}
/**************************************************************************
long int ReadShort(FILE *inflile)
DESCRIPTION: Reads a short interger from a file
**************************************************************************/
int ReadShort(FILE *infile)
{
return (fgetc(infile) << 8) | fgetc(infile);
}
/**************************************************************************
long int ReadLong(FILE *inflile)
DESCRIPTION: Reads a long interger from a file
**************************************************************************/
long int ReadLong(FILE *infile)
{
int i;
long int num = 0;
num = (unsigned char)fgetc(infile);
for(i = 0; i < 3; i++) {
num = (num << 8) | (unsigned char)fgetc(infile);
}
return(num);
}
/**************************************************************************
void playsound(SAMPLE *data,length)
DESCRIPTION: Adds a sound to the play list. If the playlist is full,
all the sounds are scooted over and the new sound is added
as the last item;
**************************************************************************/
void playsound(SAMPLE *data,DWORD length)
{
int i;
if(sounds_in_queue >= MAXSOUNDS) {
for(i= 0; i <sounds_in_queue; i++) {
sounddata[i] = sounddata[i+1];
soundpos[i] = soundpos[i+1];
}
sounds_in_queue--;
}
sounddata[sounds_in_queue] = data;
soundpos[sounds_in_queue] = length-1;
sounds_in_queue++;
}
/*********************************************************************
SAMPLE *loadwave(char *wavefile,unsigned long int *length)
DESCRIPTION: Loads a wave files (mono, 8bit)
INTPUTS:
wavefile filename of wave file
length pointer to length interger
RETURNS:
pointer to data
**********************************************************************/
SAMPLE *loadwave(char *wavefile,unsigned long int *length)
{
int i;
SAMPLE *data;
BYTE dummydata[255];
FILE *input;
DWORD rlen,flen;
WORD s_per_sec,b_per_sec,num_channels,tag;
char riffid[5],waveid[5],fmtid[5],dataid[5];
input = fopen(wavefile,"rb");
if(!input) { // If unsuccesful...
*length = 1; // set short length to prevent
// mistakes later.
return(NULL); // REturn a null pointer
}
// Get WAVE header data
fread(riffid,1,4,input); // wave files staqrt with "Riff"
riffid[4] = 0;
fread(&rlen,1,4,input); // File size
fread(waveid,1,4,input); // Wave id string ("Wave")
waveid[4] = 0;
if(strcmp(waveid,"WAVE")) { // is it a wave file?
fclose(input);
return(NULL);
}
fread(fmtid,1,4,input); // Format id string ("fmt ")
fmtid[4] = 0;
fread(&flen,1,4,input); // offset to data
if(flen > 240) flen = 240; // Just a precaution so that
// We do not overload dummydata
fread(&tag,1,2,input); // tag
fread(&num_channels,1,2,input); // number of channels
fread(&s_per_sec,1,2,input); // sample rate (hz)
fread(&b_per_sec,1,2,input); // bytes per seconf rate
fread(dummydata,1,flen-8,input); // Skip ahead
fread(dataid,1,4,input); // Dataid string
dataid[4] = 0;
fread(length,1,4,input); // length of data
data = (SAMPLE *)farmalloc(*length+1); // allocate memory for data
if(!data) { // oops. Not enough mem!
fclose(input);
return(NULL);
}
fread(data,1,*length,input); // read the data
for(i = 0; i < *length; i++) { // convert to signed format
*(data + i) = ((BYTE)*(data + i))-128;
}
fclose(input); // Wrap it up
return(data);
}
/**************************************************************************
void load_instruments(char *filename,BYTE inst[128][11])
DESCRIPTION: Loads instrument defs from a file (128)
File format: 11 hex values followed by a name. eg:
30 33 40 00 E1 E2 87 63 06 01 00 "Electric Piano 2"
33 34 00 00 92 C3 C3 B3 02 01 00 "Harpsichord"
32 32 00 00 92 C3 C3 B3 02 01 00 "Clavichord"
.
.
.
(The name is not loaded.)
The hex values are dumped into an 2-D array. The file can have
more or less than 128 defs without harm to this function.
**************************************************************************/
int load_instruments(char *filename,BYTE inst[128][11])
{
FILE *input;
int i=0,j;
char string[255];
input = fopen(filename,"r"); // open the file
if(!input) return(0);
// read it's contents
while(fgets(string,255,input) && i < 128) {
for(j = 0; j < 11; j++) sscanf(string+j*3,"%X ",&inst[i][j]);
i++;
}
// clean up and go home
fclose(input);
return(i);
}
/**************************************************************************
void freemidi(MIDI *m)
DESCRIPTION: Frees the data allocated for a MIDI structure
**************************************************************************/
void freemidi(MIDI *m)
{
int i;
for(i = 0; i < m->num_tracks; i++) free(m->track[i]);
free(m);
}